package com.bcst.plugin;

import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.RowBounds;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import java.util.Properties;

/**
  分页插件
 *@description 欢迎关注公众号 编程识堂,每日更新技术相关文章，关注我不迷路
 *@author 小堂
 *@create 2023/7/20 20:54
 */
@Intercepts({@Signature(
        method = "prepare",
        type = StatementHandler.class,
        args = {Connection.class, Integer.class}
)})
public class PageInterceptor implements Interceptor {


    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("PageInterceptor插件 目标方法被增强。。。。");
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);
        // 分离代理对象链(由于目标类可能被多个拦截器拦截，从而形成多次代理，通过下面的两次循环
        // 可以分离出最原始的的目标类)
        while (metaStatementHandler.hasGetter("h")) {
            Object object = metaStatementHandler.getValue("h");
            metaStatementHandler =   SystemMetaObject.forObject(object);
        }
        // 分离最后一个代理对象的目标类
        while (metaStatementHandler.hasGetter("target")) {
            Object object = metaStatementHandler.getValue("target");
            metaStatementHandler =   SystemMetaObject.forObject(object);
        }
        RowBounds rowBounds = null;
        try {
            rowBounds = (RowBounds)metaStatementHandler.getValue("delegate.rowBounds");
        } catch (Exception var11) {
            return invocation.proceed();
        }

        if (rowBounds != null && rowBounds != RowBounds.DEFAULT) {
            String originalSql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
            Connection connection = (Connection) invocation.getArgs()[0];
            MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
            BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
            int totalCount = this.getPageTotalCount(originalSql, connection, mappedStatement, boundSql);
            if (totalCount > 0) {
                String sql = "";
                if (rowBounds.getOffset() > 0) {
                    sql = originalSql + " limit " + rowBounds.getOffset() + "," + rowBounds.getLimit();
                } else {
                    sql = originalSql + " limit " + rowBounds.getLimit();
                }
                // 采用物理分页后，就不需要mybatis的内存分页了，所以重置下面的两个参数
                metaStatementHandler.setValue("delegate.boundSql.sql", sql);
                metaStatementHandler.setValue("delegate.rowBounds.offset", RowBounds.NO_ROW_OFFSET);
                metaStatementHandler.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT);
            }
            metaStatementHandler.setValue("delegate.boundSql.parameterObject.totalCount", totalCount);
        }
        // 将执行权交给下一个拦截器
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        //当目标类是StatementHandler类型时，才包装目标类，否者直接返回目标本身,减少目标被代理的
        if (target instanceof StatementHandler){
            System.out.println("PageInterceptor插件将要包装的⽬标对象："+target);
            return Plugin.wrap(target,this);
        }
        return target;
    }

    @Override
    public void setProperties(Properties properties) {

    }

    private int getPageTotalCount(String sql, Connection connection, MappedStatement mappedStatement, BoundSql boundSql) {
        String countSql = "select count(0) from (" + sql + ") as total";
        PreparedStatement countStmt = null;
        ResultSet rs = null;
        int totalCount = 0;
        try {
            countStmt = connection.prepareStatement(countSql);

            BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql, boundSql.getParameterMappings(), boundSql.getParameterObject());
            additionalParameter(boundSql,countBS);
            this.setParameters(countStmt, mappedStatement, countBS, boundSql.getParameterObject());
            rs = countStmt.executeQuery();
            if (rs.next()) {
                totalCount = rs.getInt(1);
            }
        } catch (SQLException var22) {

        } finally {
            try {
                rs.close();
            } catch (SQLException var21) {
                var21.printStackTrace();
            }

            try {
                countStmt.close();
            } catch (SQLException var20) {
                var20.printStackTrace();
            }

        }

        return totalCount;
    }

    private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql, Object parameterObject) throws SQLException {
        ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
        parameterHandler.setParameters(ps);
    }

    private void additionalParameter(BoundSql boundSql,BoundSql targetBoundSql){
        Class c = boundSql.getClass();
        try {
            Field field = c.getDeclaredField("additionalParameters");
            field.setAccessible(true);
            Map<String,Object> boundMap = (Map<String,Object>)field.get(boundSql);
            if(boundMap!=null) {
                for (Map.Entry<String, Object> entry : boundMap.entrySet()) {
                    targetBoundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
                }
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
}
